/*
 * SPDX-FileCopyrightText: 2023 Andrew Gunnerson
 * SPDX-License-Identifier: GPL-3.0-only
 */

package com.sanji.audio_test.template

import android.content.Context
import android.content.res.Configuration
import android.text.Spannable
import android.text.style.ForegroundColorSpan
import androidx.annotation.ColorRes
import androidx.core.text.getSpans
import com.sanji.audio_test.R
import com.google.android.material.color.ColorRoles
import com.google.android.material.color.MaterialColors

/**
 * 模板语法高亮器
 * 
 * 用于对模板字符串进行语法高亮显示，包括变量引用、回退字符和目录分隔符等元素
 */
class TemplateSyntaxHighlighter(context: Context) {
    // 变量引用名称的颜色
    private val colorVariableRefName = "#ff008000"
    // 变量引用参数的颜色
    private val colorVariableRefArg = "#ff0000ff"
    // 回退字符的颜色（[、]、|）
    private val colorFallbackChars = "#ffff00ff"
    // 目录分隔符的颜色（/）
    private val colorDirectorySeparator = "#ffff0000"

    /**
     * 对可变文本进行语法高亮
     * 
     * @param spannable 要高亮的可变文本
     * @param templateStart 模板开始位置（默认为0）
     * @param templateEnd 模板结束位置（默认为文本长度）
     */
    fun highlight(
        spannable: Spannable,
        templateStart: Int = 0,
        templateEnd: Int = spannable.length,
    ) {
        val template = if (templateStart == 0 && templateEnd == spannable.length) {
            spannable
        } else {
            spannable.subSequence(templateStart, templateEnd)
        }

        // 每次都强制重新着色。我们不依赖Android的span扩展，因为它们不够复杂，无法保持语法高亮的正确性。
        for (span in spannable.getSpans<ForegroundColorSpan>()) {
            val start = spannable.getSpanStart(span)
            val end = spannable.getSpanEnd(span)
            if (start >= templateStart && end <= templateEnd) {
                spannable.removeSpan(span)
            }
        }

        // 这是有意不基于AST的，因为Template无法解析不完整的半写模板，
        // 我们不希望在用户输入时移除语法高亮。
        for ((i, c) in template.withIndex()) {
            if (c == '[' || c == ']' || c == '|') {
                spannable.setSpan(
                    ForegroundColorSpan(colorFallbackChars.accent),
                    templateStart + i,
                    templateStart + i + 1,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
                )
            }
        }

        // 高亮变量引用
        for (m in REGEX_VARIABLE_REF.findAll(template)) {
            spannable.setSpan(
                ForegroundColorSpan(colorVariableRefName.accent),
                templateStart + m.range.first,
                templateStart + m.range.last + 1,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
            )

            val arg = m.groups[1]
            if (arg != null) {
                spannable.setSpan(
                    ForegroundColorSpan(colorVariableRefArg.accent),
                    templateStart + arg.range.first,
                    templateStart + arg.range.last + 1,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
                )
            }
        }

        // 最后高亮目录分隔符，以确保其视觉突出性。
        for ((i, c) in template.withIndex()) {
            if (c == '/') {
                spannable.setSpan(
                    ForegroundColorSpan(colorDirectorySeparator.accent),
                    templateStart + i,
                    templateStart + i + 1,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
                )
            }
        }
    }

    companion object {
        // Android的正则表达式实现需要这些"冗余"的转义
        @Suppress("RegExpRedundantEscape")
        // 匹配变量引用的正则表达式：{变量名:参数}
        private val REGEX_VARIABLE_REF = "\\{[\\p{Alpha}_][\\p{Alpha}\\d_]*(:[^\\}]*)?\\}".toRegex()

        /**
         * 获取协调后的颜色
         * 
         * @param context 上下文
         * @param colorResId 颜色资源ID
         * @return 颜色角色对象
         */
        private fun getHarmonizedColor(context: Context, @ColorRes colorResId: Int): ColorRoles {
            // 判断是否为浅色主题
            val isLight = context.resources.configuration.uiMode and
                    Configuration.UI_MODE_NIGHT_MASK != Configuration.UI_MODE_NIGHT_YES
            val color = context.getColor(colorResId)

            // 与主色调协调
            val blended = MaterialColors.harmonizeWithPrimary(context, color)
            return MaterialColors.getColorRoles(blended, isLight)
        }
    }
}